package com.leigq.www.producer.config;

import com.leigq.www.common.enumeration.QueueEnum;
import com.leigq.www.producer.constant.QueueConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * 队列相关配置，JavaConfig 方式，本项目普通的队列使用的是全注解方式，JavaConfig 仅仅实现了延迟队列相关配置作为参考
 * <br/>
 * 延迟队列参考：
 * <ul>
 *     <li><a href='https://lizhou.blog.csdn.net/article/details/123856087'>SpringBoot中整合RabbitMQ实现延时队列（死信队列篇）</a></li>
 *     <li><a href='https://blog.csdn.net/qq_40065776/article/details/113917675'>SpringBoot中整合RabbitMQ实现延时队列（延时插件篇）</a></li>
 * </ul>
 * <p>
 * <br/>
 * 纯注解方式实现，在消费端使用注解，即可自动创建队列及绑定关系
 * <br/>
 *
 * @author ：leigq
 * @date ：2019/7/25 20:14
 */
@Configuration
@Slf4j
public class QueueConfig implements InitializingBean {

    @Autowired
    private RabbitAdmin rabbitAdmin;

    /* 延迟消息队列实现方式有 2 种 */

    /* 一、利用TTL DLX实现延时队列的方式 */

    /**
     * 普通的队列1
     * <br/>
     * 方式一实现的
     * <pre>
     * TTL
     * RabbitMQ可以针对Message设置x-expires(对消息进行单独设置，每条消息TTL可以不同)，或者针对队列设置x-message-ttl(则队列中所有的消息都有相同的过期时间)
     * 来控制消息的生存时间，如果超时(两者同时设置以最先到期的时间为准)，则消息变为dead letter(死信)
     *
     * Dead Letter Exchanges（DLX）
     * RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key（可选）两个参数，
     * 如果队列内出现了dead letter，则按照这两个参数重新路由转发到指定的队列。
     * x-dead-letter-exchange：出现dead letter之后将dead letter重新发送到指定exchange
     * x-dead-letter-routing-key：出现dead letter之后将dead letter重新按照指定的routing-key发送
     * </pre>
     *
     * @return the queue
     */
    @Bean
    public Queue normalQueue1() {
        return QueueBuilder.durable(QueueEnum.NORMAL_QUEUE1.getQueueName())
                // DLX, x-dead-letter-exchange 声明了队列里的死信转发到的DLX(出现dead letter之后将dead letter重新发送到指定exchange)名称，也就是实际最终消费的queue
                .withArgument("x-dead-letter-exchange", QueueEnum.DELAY_DLX_QUEUE1.getExchangeName())
                // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。出现dead letter之后将dead letter重新按照指定的routing-key发送
                .withArgument("x-dead-letter-routing-key", QueueEnum.DELAY_DLX_QUEUE1.getRoutingKey())
                // 设置队列的过期时间，如果设置了队列的TTL属性，那么一旦消息过期，就会被队列丢弃(如果配置了死信队列被丢到死信队列中)。
                // 也可以在发消息时，针对每条消息设置TTL，两者同时设置以最先到期的时间为准
                .withArgument("x-message-ttl", QueueConstant.NORMAL_QUEUE1_EXPIRATION)
                .build();
    }


    /**
     * 普通的队列1的交换机
     * <br/>
     * 方式一实现的
     *
     * @return the direct exchange
     */
    @Bean
    public DirectExchange normalQueue1Exchange() {
        return (DirectExchange) ExchangeBuilder.directExchange(QueueEnum.NORMAL_QUEUE1.getExchangeName())
                .durable(true)
                .build();
    }


    /**
     * 普通的队列1绑定到交换机
     * <br/>
     * 方式一实现的
     *
     * @return the binding
     */
    @Bean
    public Binding normalQueue1Binding(Queue normalQueue1, DirectExchange normalQueue1Exchange) {
        return BindingBuilder.bind(normalQueue1)
                .to(normalQueue1Exchange)
                .with(QueueEnum.NORMAL_QUEUE1.getRoutingKey());
    }


    /**
     * 普通队列1延时后的消费队列（实际消费队列）
     * <br/>
     * 方式一实现的
     */
    @Bean
    public Queue delayDlxQueue1() {
        return QueueBuilder
                .durable(QueueEnum.DELAY_DLX_QUEUE1.getQueueName())
                .build();
    }


    /**
     * 普通队列1延时后的消费队列（实际消费队列）的交换机
     * <br/>
     * 方式一实现的
     *
     * @return the direct exchange
     */
    @Bean
    public DirectExchange delayDlxQueue1Exchange() {
        return (DirectExchange) ExchangeBuilder.directExchange(QueueEnum.DELAY_DLX_QUEUE1.getExchangeName())
                .durable(true)
                .build();
    }


    /**
     * 延时队列1的实际消费队列绑定到交换机
     * <br/>
     * 方式一实现的
     *
     * @return the binding
     */
    @Bean
    public Binding delayDlxQueue1Binding(Queue delayDlxQueue1, DirectExchange delayDlxQueue1Exchange) {
        return BindingBuilder.bind(delayDlxQueue1)
                .to(delayDlxQueue1Exchange)
                .with(QueueEnum.DELAY_DLX_QUEUE1.getRoutingKey());
    }







    /* 二、利用Rabbitmq的插件x-delay-message实现 */

    /**
     * 延时队列2
     * <br/>
     * 方式二实现的
     *
     * @return the queue
     */
    @Bean
    public Queue delayQueue2() {
        return QueueBuilder
                .durable(QueueEnum.DELAY_QUEUE2.getQueueName())
                .build();
    }

    /**
     * 延时队列2的交换机
     * <br/>
     * 专门给延时队列定义的一种交换机类型
     * <br/>
     * 方式二实现的
     *
     * @return the custom exchange
     */
    @Bean
    public CustomExchange delayQueue2Exchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(QueueEnum.DELAY_QUEUE2.getExchangeName(), "x-delayed-message", true, false, args);
    }

    /**
     * 延时队列2绑定到交换机
     * <br/>
     * 方式二实现的
     *
     * @param delayQueue2         the delay queue 2
     * @param delayQueue2Exchange the delay queue 2 exchange
     * @return the binding
     */
    @Bean
    public Binding delayQueue2Binding(Queue delayQueue2, CustomExchange delayQueue2Exchange) {
        return BindingBuilder.bind(delayQueue2)
                .to(delayQueue2Exchange)
                .with(QueueEnum.DELAY_QUEUE2.getRoutingKey())
                .noargs();
    }


    /**
     * 创建初始化RabbitAdmin对象
     *
     * @param connectionFactory the connection factory
     * @return the rabbit admin
     */
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        // 只有设置为 true，spring 才会加载 RabbitAdmin 这个类
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }

    /**
     * 启动服务后便自动创建交换机，队列，绑定关系
     */
    @Override
    public void afterPropertiesSet() {
        rabbitAdmin.declareQueue(normalQueue1());
        rabbitAdmin.declareExchange(normalQueue1Exchange());

        rabbitAdmin.declareQueue(delayDlxQueue1());
        rabbitAdmin.declareExchange(delayDlxQueue1Exchange());

        rabbitAdmin.declareQueue(delayQueue2());
        rabbitAdmin.declareExchange(delayQueue2Exchange());
    }
}
